/*
 *  Copyright 2006 www.webtair.com
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.webtair.session;

import java.util.*;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.webtair.session.artifact.SessionInfo;
import com.webtair.session.config.ConfigBean;

/**
 * Entry poin of SessionMan library.
 * Class that implements all buisness logic 
 * for session management.
 * 
 * @author Vaclav (Vyacheslav Yakovenko)
 */
public class SessionMan<T extends SessionInfo> {

	/** Config info of SessionMan */
	private ConfigBean config;

	/** Logger for SessionMan */
	@SuppressWarnings("unused")
	private static Log logger = LogFactory.getLog(SessionMan.class);
	
	/** Repository of idSessions and corresponded data */
	private HashMap<String, T> sessions;

	/**
	 * Constructor that populate SessionMan
	 * by provided config information
	 * @param config - config bean
	 */
	public SessionMan(ConfigBean config) {
		if (config == null){
			this.config = new ConfigBean();
		}else{
			this.config = config;
		}
		sessions = new HashMap<String, T>(this.config.getUserCount());
		new SessionsCleaner().start();
	}
	
	/**
	 * Default constructor
	 */
	public SessionMan(){
		this(null);
	}
	
	/**
	 * Generates unique session id for current login
	 * 
	 * @param login
	 * @return unique idSession
	 */
	public static String generateSessionId(String login) {
		return DigestUtils.md5Hex(login + Calendar.getInstance()
				+ java.lang.Math.random());
	}
	
	/**
	 * Register new user at SessionMan's HashMap. 
	 * Each user can have so much sessions as he want.
	 * 
	 * @param user - user info
	 * @return idSession - newly created session
	 */
	public String putSessionInfo(String login, T sessionInfo) {
		// create new session id
		String idSession = SessionMan.generateSessionId(login);
		synchronized (sessions) {
			sessions.put(idSession, sessionInfo);
		}
		return idSession;
	}
	
	/**
	 * Check if session is alive and update lastSessionUsedTime if true.
	 * 
	 * @param idSession
	 * @return true if session is alive, and false if idSession not found or
	 *         session is expired.
	 */
	public boolean validateSession(String idSession) {

		if (isSessionExpired(idSession)) {
			synchronized (sessions) {
				sessions.remove(idSession);
				return false;
			}
		} else {
			SessionInfo info = sessions.get(idSession);
			synchronized (sessions) {
				if (info != null) {
					info.setLastTimeUsed(Calendar.getInstance()
							.getTimeInMillis());
					return true;
				} else {
					return false;
				}// if
			}
		}// if
	}

	/**
	 * Check if session expired or not.
	 * 
	 * @param idSession
	 * @return true if and only if session expired.
	 */
	public boolean isSessionExpired(String idSession) {

		SessionInfo info = sessions.get(idSession);
		if (info == null) {
			return true;
		}
		long interval = Calendar.getInstance().getTimeInMillis()
				- info.getLastTimeUsed();
		if (interval > config.getSessionLifeTime() ) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Remove session from session handler map
	 * @param idSession - session id
	 */
	public void removeSession(String idSession) {
		synchronized(sessions){
			sessions.remove(idSession);
		}
		System.out.println("removeSession(), after idSession: " 
				+ sessions.containsKey(idSession) );
	}

	/**
	 * Util class, that periodicaly must clean sessions HashMap from expired
	 * session to prevent from memory leak.
	 * 
	 * @author vaclav
	 */
	private class SessionsCleaner extends Thread {

		/**
		 * run-method that'll stroll throughout HashMap
		 * and cleanup it from expired sessions
		 */
		public void run() {

			while (true) {
				System.out.println("SessionsCleaner : thread sleep");
				logger.debug("SessionsCleaner : thread sleep");
				try {
					Thread.sleep(config.getCleanupInterval() );
				} catch (InterruptedException e) {
					return;
				}
				synchronized (sessions) {
					System.out
							.println("SessionsCleaner : hashmap size before cleanup: "
									+ sessions.size());
					logger
							.debug("SessionsCleaner : hashmap size before cleanup: "
									+ sessions.size());
					Set<String> sessToRemove = new HashSet<String>();
					for (String key : sessions.keySet()) {
						// collect all expired session's id
						if (isSessionExpired(key)) {
							sessToRemove.add(key);
						}
					}
					// remove all expired sessions
					for (String key : sessToRemove) {
						sessions.remove(key);
					}
					System.out
							.println("SessionsCleaner : hashmap size after cleanup: "
									+ sessions.size());
					logger
							.debug("SessionsCleaner : hashmap size after cleanup: "
									+ sessions.size());
				}
			}
		}
	}// SessionsCleaner
	
}
